#include "FontRenderer.h"
#include <algorithm>
#include <iostream>
#include <iterator>

using namespace std;

bool compareGDDescendingArea(const GlyphDescription& gd1, const GlyphDescription& gd2)
{
	return gd1.width*gd1.height > gd2.width*gd2.height;
}

bool compareGDAscendingID(const GlyphDescription& gd1, const GlyphDescription& gd2)
{
	return gd1.glyphIndex < gd2.glyphIndex;
}

FaceDescription::~FaceDescription()
{
	//delete glyphs;
	//delete texture;
}

GlyphNode::GlyphNode()
	: gd(nullptr), left(0), top(0), width(0), height(0)
{
	child[0]=nullptr;
	child[1]=nullptr;
}

GlyphNode::~GlyphNode()
{
	if (child[0] != nullptr)
	{
		delete child[0];
		delete child[1];
	}
}

GlyphNode* GlyphNode::insert(GlyphDescription* gdInsert)
{
	// inner node
	if (child[0]!=nullptr)
	{
		GlyphNode* newNode = child[0]->insert(gdInsert);
		if (newNode != nullptr)
			return newNode;

		return child[1]->insert(gdInsert);
	}
	// leaf node
	else
	{
		// leaf occupied
		if (gd != nullptr)
			return nullptr;

		// glyph doesn't fit
		if (gdInsert->width > width || gdInsert->height > height)
			return nullptr;

		if (gdInsert->width == width && gdInsert->height == height)
		{
			gd = gdInsert;
			gdInsert->left = left;
			gdInsert->top = top;

			return this;
		}

		child[0] = new GlyphNode();
		child[1] = new GlyphNode();

		int dw = width - gdInsert->width;
		int dh = height - gdInsert->height;

		//  ______
		// | |    |
		// |_|____|
		//
		if (dw > dh)
		{
			child[0]->left = left;
			child[0]->width = gdInsert->width;
			child[0]->top = top;
			child[0]->height = height;

			child[1]->left = left+gdInsert->width;
			child[1]->width = dw;
			child[1]->top = top;
			child[1]->height = height;
		}
		//  _____
		// |_____|
		// |     |
		// |     |
		// |_____|
		//
		else
		{
			child[0]->left = left;
			child[0]->width = width;
			child[0]->top = top;
			child[0]->height = gdInsert->height;

			child[1]->left = left;
			child[1]->width = width;
			child[1]->top = top+gdInsert->height;
			child[1]->height = dh;
		}

		return child[0]->insert(gdInsert);
	}
}

FontRenderer::FontRenderer()
{
	if(FT_Init_FreeType(&_ftLibrary))
	{
		cout << "ERR: Could not init FreeType library" << endl;
		return;
	}

	glPrimitiveRestartIndex(-1);
	glEnable(GL_PRIMITIVE_RESTART);

	_fontShader = new GLProgram("Font Shader");
	_fontShader->attach(GL_VERTEX_SHADER, "shaders/fontshader.vert");
	_fontShader->attach(GL_FRAGMENT_SHADER, "shaders/fontshader.frag");
	_fontShader->link();

	_fontShaderUniformLocations.color = _fontShader->getUniformLocation("color");
	_fontShaderUniformLocations.invImageSize = _fontShader->getUniformLocation("invImageSize");
	_fontShaderUniformLocations.pOrigin = _fontShader->getUniformLocation("pOrigin");
	_fontShaderUniformLocations.texture0 = _fontShader->getUniformLocation("texture0");
}

FontRenderer::~FontRenderer()
{
	delete _fontShader;
}

void FontRenderer::loadFont(const std::string& fontPath, const std::string& fontName, int pixelHeight)
{
	FT_Face face;
	if (FT_New_Face(_ftLibrary, fontPath.c_str(), 0, &face))
	{
		cout << "ERR: Could not load font face" << endl;
		return;
	}

	if (FT_Set_Pixel_Sizes(face, 0, pixelHeight))
	{
		cout << "ERR: Could not load font with height of " << pixelHeight << "px" << endl;
		return;
	}

	FT_GlyphSlot g = face->glyph;
	std::vector<GlyphDescription>* glyphs = new std::vector<GlyphDescription>();

	// Load metrics of all glyphs
	int totalArea = 0;
	for(FT_UInt i=0; i < (FT_UInt)face->num_glyphs; i++)
	{
		if (!FT_Load_Glyph(face, i, FT_LOAD_DEFAULT))
		{
			GlyphDescription gd;
			gd.glyphIndex = i;
			gd.width = g->metrics.width/64;
			gd.height = g->metrics.height/64;
			gd.bearingX = g->metrics.horiBearingX/64;
			gd.bearingY = g->metrics.horiBearingY/64;
			gd.advance = g->metrics.horiAdvance/64;
			gd.left = 0;
			gd.top = 0;

			glyphs->push_back(gd);

			totalArea += gd.width*gd.height;
		}
	}

	std::sort(glyphs->begin(), glyphs->end(), compareGDDescendingArea);

	// Pack glyphs into kdtree
	int insertedGlyphs=0;
	int maxSide=0;
	float multiplier = 1.05f;
	do {
		insertedGlyphs = 0;
		maxSide = (int)sqrt(totalArea*multiplier);
		multiplier += 0.05f;

		GlyphNode* rootNode = new GlyphNode();
		rootNode->width = maxSide;
		rootNode->height = maxSide;

		for(vector<GlyphDescription>::iterator iter = glyphs->begin();
			iter != glyphs->end();
			iter++)
		{
			insertedGlyphs += rootNode->insert(&*iter)==nullptr?0:1;
		}

		delete rootNode;
	} while (insertedGlyphs != glyphs->size());

	std::sort(glyphs->begin(), glyphs->end(), compareGDAscendingID);

	// dump glyphs into atlas
	unsigned char *atlas = new unsigned char[maxSide*maxSide];
	std::fill_n(stdext::checked_array_iterator<unsigned char*>(atlas, maxSide*maxSide),maxSide*maxSide,0);

	for(vector<GlyphDescription>::iterator iter = glyphs->begin();
		iter != glyphs->end();
		iter++)
	{
		int glyphIndex = iter->glyphIndex;
		int left = iter->left;
		int top = iter->top;
		int width = iter->width;
		int height = iter->height;

		if (!FT_Load_Glyph(face, glyphIndex, FT_LOAD_RENDER))
		{
			for (int i=0;i<height;i++)
			{
				for (int j=0;j<width;j++)
				{
					atlas[maxSide*(top+i)+left+j] = g->bitmap.buffer[width*i+j];
				}
			}
		}
	}

	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	FaceDescription fd;

	fd.texture = GLTexture::newImmutable(GL_TEXTURE_2D, GL_R8, maxSide, maxSide, GL_RED, GL_UNSIGNED_BYTE, atlas);
	fd.texture->setFilters(GL_NEAREST, GL_NEAREST);
	fd.texture->setWrap(GL_CLAMP_TO_EDGE);
	
	delete[] atlas;

	fd.fontPath = fontPath;
	fd.pixelHeight = pixelHeight;
	fd.glyphs = glyphs;
	fd.face = face;

	_faces[fontName] = fd;
}

/*
void FontRenderer::render(Shader* shader)
{
	FaceDescription& fd = _faces["Lucida"];

	Mesh* mesh = new Mesh();
	Geometry::createScreenQuad(mesh, true);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, fd.textureHandle);
	glUniform1i(shader->getUniformLocation("texture0"), 0);

	mesh->bindVAO();
	mesh->draw();
	mesh->unbindVAO();

	delete mesh;
}
*/

Text* FontRenderer::createText(const std::string& text, const std::string& fontName, unsigned maxLength)
{
	Text* textObj = new Text();
	textObj->init(text, &(_faces[fontName]), _fontShader, _fontShaderUniformLocations, maxLength);
	return textObj;
}

void FontRenderer::setWindowSize(float width, float height)
{
	_fontShader->use();
	glUniform2f(_fontShaderUniformLocations.invImageSize, 1.f / width, 1.f / height);
}

Text::Text()
	: _text(""), _faceDescr(nullptr), _vao(nullptr), _initialized(false)
{
	for (unsigned i = 0; i < 3; i++)
		_vbo[i] = nullptr;

	_color[0] = _color[1] = _color[2] = 0.0f;
}

Text::~Text()
{
	if (_initialized)
	{
		delete _vao;
		for (unsigned i = 0; i < 3; i++)
			delete _vbo[i];
	}
}

void Text::init(const std::string& text, FaceDescription* faceDescr, GLProgram* shader, FontShaderUniformLocations uniformLocations, unsigned maxLength)
{
	_uniformLocations = uniformLocations;
	_initialized = true;
	_maxLength = maxLength;

	_faceDescr = faceDescr;
	_shader = shader;

	if (maxLength == 0)
		maxLength = text.length();

	_vbo[0] = GLBuffer::newMutable(GL_ARRAY_BUFFER, sizeof(GLfloat) * 12 * maxLength, 0, GL_DYNAMIC_DRAW); // vertex positions
	_vbo[1] = GLBuffer::newMutable(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8 * maxLength, 0, GL_DYNAMIC_DRAW); // texture coordinates

	// init index array
	GLuint* indexArray = new GLuint[maxLength * 7];

	unsigned c = 0;
	for (unsigned i = 0; i < maxLength; i++)
	{
		unsigned s = 4 * i;
		indexArray[c++] = s;
		indexArray[c++] = s + 1;
		indexArray[c++] = s + 2;
		indexArray[c++] = s + 2;
		indexArray[c++] = s + 1;
		indexArray[c++] = s + 3;
		indexArray[c++] = -1;
	}

	_vbo[2] = GLBuffer::newMutable(GL_ELEMENT_ARRAY_BUFFER, sizeof(float) * maxLength * 7, indexArray, GL_STATIC_DRAW);

	delete[] indexArray;

	// VAO
	_vao = new GLVertexArray();
	_vao->attach(_vbo[0], 0, 3, GL_FLOAT);
	_vao->attach(_vbo[1], 1, 2, GL_FLOAT);
	_vao->attach(_vbo[2]);

	setText(text);
}

void Text::setText(const std::string& text)
{
	_text = text;

	float invTexWidth = 1.f / (float)_faceDescr->texture->width;
	float invTexHeight = 1.f / (float)_faceDescr->texture->height;

	float penX = 0.f, penY = 0.f;

	_length = min(_maxLength, _text.length());

	GLfloat *vertices = new GLfloat[12 * _length];
	GLfloat *texCoords = new GLfloat[8 * _length];

	int ti = 0, vi = 0, ii = 0;
	

	for (unsigned i = 0; i < _length; i++)
	{
		FT_UInt glyph = FT_Get_Char_Index(_faceDescr->face, _text[i]);
		GlyphDescription& gd = _faceDescr->glyphs->at(glyph);

		float leftT = (float)gd.left * invTexWidth;
		float rightT = (float)(gd.left+gd.width) * invTexWidth;
		float bottomT = (float)(gd.top+gd.height) * invTexHeight;
		float topT = (float)gd.top * invTexHeight;

		float leftV = penX + (float)gd.bearingX;
		float rightV = leftV + (float)gd.width;
		float topV = penY + (float)gd.bearingY;
		float bottomV = topV - (float)gd.height;
		
		texCoords[ti++] = leftT;
		texCoords[ti++] = topT;
		texCoords[ti++] = leftT;
		texCoords[ti++] = bottomT;
		texCoords[ti++] = rightT;
		texCoords[ti++] = topT;
		texCoords[ti++] = rightT;
		texCoords[ti++] = bottomT;

		vertices[vi++] = leftV;
		vertices[vi++] = topV;
		vertices[vi++] = 0.f;
		vertices[vi++] = leftV;
		vertices[vi++] = bottomV;
		vertices[vi++] = 0.f;
		vertices[vi++] = rightV;
		vertices[vi++] = topV;
		vertices[vi++] = 0.f;
		vertices[vi++] = rightV;
		vertices[vi++] = bottomV;
		vertices[vi++] = 0.f;


		penX += gd.advance;
	}

	_width = penX;
	_height = (float)_faceDescr->pixelHeight;

	glBindBuffer(GL_ARRAY_BUFFER, _vbo[0]->name);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * _length * 12, vertices);
	glBindBuffer(GL_ARRAY_BUFFER, _vbo[1]->name);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GLfloat) * _length * 8, texCoords);

	delete[] vertices;
	delete[] texCoords;
}

const std::string& Text::getText() const
{
	return _text;
}

float Text::getWidth() const
{
	return _width;
}

float Text::getHeight() const
{
	return _height;
}

int Text::getOriginX() const
{
	return _pX;
}

int Text::getOriginY() const
{
	return _pY;
}

void Text::render()
{
	_shader->use();
	
	glUniform2f(_uniformLocations.pOrigin, (float)_pX, (float)_pY);
	glUniform3f(_uniformLocations.color, _color[0], _color[1], _color[2]);

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, _faceDescr->texture->name);

	glBindVertexArray(_vao->name);
	glDrawElements(GL_TRIANGLES, 7 * _length, GL_UNSIGNED_INT, 0);
	
	glBindVertexArray(0);
}

void Text::setPosition(int pX, int pY)
{
	_pX = pX;
	_pY = pY;
}

void Text::setColor(float r, float g, float b)
{
	_color[0] = r;
	_color[1] = g;
	_color[2] = b;
}